package BitCreeekClient;

import BitCreekLib.*;

import java.net.InetAddress;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class Client {

	/**
     * ThreadPool per effettuare mandare messaggi keep alive al Tracker
     */
    private ExecutorService tpUDP;
    /**
     * ThreadPool per effettuare mandare richieste TCP al Tracker per l'aggiornamento dello swarm
     */
    private ExecutorService tpTCP;
    /**
     * ThreadPool per i messaggi di upload mandati dagli altri Peer
     */
    private ExecutorService p2pTCPupload;
    /**
     * ThreadPool per i messaggi di download mandati verso altri Peer
     */
    private ExecutorService p2pTCPdownload;

    /**
     * Massimo e minimo numero delle porte utilizzabili per bindare un torrent
     */
    private int minPort, maxPort;

    private ArrayList<Integer> portsUsed;

    /**
     * Generatore random di porte
     */
    private Random randomGenerator;

    /**
     * Ip del Tracker
     */
    protected static String TrackerIP;
    
    /**
     * HashTable contente tutti quanti i download effettuati dal client ( upload/download ). 
     * La chiava contiene il nome del file, il valore contiene l'istanza del download.
     */
    public static Hashtable<String, Download> downloads;

    /**
     * Inizializzazione delle impostazioni del client.
     */
    public void intit () {

        Log.add ( "[ Client.init() ]: Initializing Client Torrent" );

        minPort = 10000;
        maxPort = 14000;

        portsUsed = new ArrayList<Integer> ();
        randomGenerator = new Random ();
        downloads = new Hashtable<String, Download> ();

        tpUDP = Executors.newCachedThreadPool ();
        tpTCP = Executors.newCachedThreadPool ();
        p2pTCPupload = Executors.newCachedThreadPool ();
        p2pTCPdownload = Executors.newCachedThreadPool ();

    }

    /**
     * Generatore di porte random non utilizate
     *
     * @return Una porta random fra minPort e maxPort non utilizata
     */
    private int randomPort () {
        int r;
        int range = ( this.maxPort - this.minPort ) + 1;

        do {
            r = randomGenerator.nextInt ( range );
        } while ( this.portsUsed.contains ( r + this.minPort ) );

        this.portsUsed.add ( r + this.minPort );
        return r + this.minPort;
    }

    /**
     * Ricerca di un torrent attraverso un espressione regolare utilizzando RMI Tracker
     * @param fileName
     * @throws Exception
     * @return I torrent che sono stati trovati tramite la ricerca con l'espressione regolare
     */
    public ArrayList<MetaInfo> searchTorrentByName ( String fileName ) throws Exception {
        ArrayList<MetaInfo> matches;
        Log.add ( "Requesting: " + fileName );
        Registry r = LocateRegistry.getRegistry ( Client.TrackerIP, PeerConfig.getServerPort() );
        IRMITracker h = ( IRMITracker ) r.lookup ( "RMITracker" );
        matches = h.searchTorrent ( fileName );
        Log.add ( "Torrents matched \"" + fileName + "\" :" );
        return matches;
    }

    /**
     * Inizializzazione del download di un determinato torrent.
     * @param torrent
     * @param destinationPath
     * @throws Exception
     */
    public void startDownload ( MetaInfo torrent, String destinationPath ) throws Exception {
        Download download;
        download = new Download ();
        download.trackerIP = torrent.getTrackerAddress ().getHostAddress ();

        download.localPath = destinationPath;
        download.metaInfo = torrent;
        networkInit ( download, action_type.DOWNLOAD );
    }

    public boolean checkExistingDownload( MetaInfo torrent ) {
        //Al momento controlla solo il nome del torrent se e' presente nella lista.
        return downloads.containsKey( torrent.getName() );

    }

    /**
     * Pubblicazione di un torrent.
     * @param localPath
     * @throws Exception
     */
    public void publishTorrent ( String localPath ) throws Exception {
        Log.add ( "[ Client.startDownload() ]: Publishing file:" + localPath );
        Download download;

        download = new Download ();
        download.trackerIP = Client.TrackerIP;
        download.torrentFormFile ( localPath );        
        networkInit ( download, action_type.PUBLISH );
    }

    public void networkInit ( Download download, action_type type ) throws Exception {
        int clientP2PPortUpload;
        int clientP2PPortDownload;
        Ports ports = null;

        clientP2PPortUpload = randomPort ();
        clientP2PPortDownload = randomPort ();

        Registry r = LocateRegistry.getRegistry ( download.trackerIP, 6800 );
        IRMITracker h = ( IRMITracker ) r.lookup ( "RMITracker" );

        PeerServices callbackObj = new PeerServices ();
        IRMIPeer stub = ( IRMIPeer ) UnicastRemoteObject.exportObject ( callbackObj, 0 );

        switch ( type ) {
            case DOWNLOAD: {
                ports = download.metaInfo.getTrackerPorts ();
                h.getTorrent ( download.getName (), new Host ( InetAddress.getByName(InetAddress.getLocalHost ().getHostAddress()), clientP2PPortUpload ) );
                download.torrentFromMetaInfo ( download.metaInfo, download.localPath );
                download.initDownload ();
                break;
            }
            case PUBLISH:
                h.publishTorrent ( InetAddress.getByName(InetAddress.getLocalHost().getHostAddress()), clientP2PPortUpload, download.metaInfo, stub );
                ports = h.getTorrentPorts ( download.getName() );
                download.metaInfo.setTrackerPorts ( ports );
                break;
            case RESTORE:
                // traker non ha descrittore
                if ( h.getTorrent ( download.getName (), new Host ( InetAddress.getByName ( download.trackerIP ), clientP2PPortUpload ) ) == null ) {
                    publishTorrent ( download.localPath );
                    return;
                }
                ports = download.metaInfo.getTrackerPorts ();
                ports = h.getTorrentPorts(download.getName ());
                download.metaInfo.setTrackerPorts ( ports );
                break;
        }
        downloads.put ( download.metaInfo.getName (), download );

        Log.add ( "[ Client.networkInit() ]: Running thread PeerTCP active on port: " + ports.getTcpPort () );
        Host trackerTCP = new Host ( InetAddress.getByName ( download.trackerIP ), ports.getTcpPort () );
        Log.add ( "[ Client.networkInit() ]: Running thread PeerUDP active on port: " + ports.getUdpPort () );
        Host trackerUDP = new Host ( InetAddress.getByName ( download.trackerIP ), ports.getUdpPort () );

        tpTCP.execute ( new PeerTCPThread ( trackerTCP, download.getName () ) );
        tpUDP.execute ( new PeerUDPThread ( trackerUDP, new Host ( InetAddress.getLocalHost (), clientP2PPortUpload ), download.getName () ) );

        Log.add ( "[ Client.networkInit() ]: Running thread P2P_TCP ( Upload ) on port: " + clientP2PPortUpload );
        PeerP2PServerThread p2pServerThread = new PeerP2PServerThread ( clientP2PPortUpload, download.getName () );
        p2pTCPupload.execute ( p2pServerThread );

        Log.add ( "[ Client.networkInit() ]: Running thread P2P_TCP( Download ) on port: " + clientP2PPortDownload );
        PeerP2PClientThread p2PThreadClient = new PeerP2PClientThread ( clientP2PPortDownload, download.getName () );
        p2pTCPdownload.execute ( p2PThreadClient );
        applicationWindow.c.fireGUIEvent ( new GUIEvent ( download ) );
    }

    public enum action_type {
        PUBLISH,
        DOWNLOAD,
        RESTORE
    }
}
